package cn.bearspark.framework.redis.starter.buffer.hash.un;

import cn.bearspark.framework.redis.starter.buffer.hash.RedisBaseHashBuffer;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Redis 的 hash 数据结构的缓存器的抽象父类
 * <p>
 * 注：本缓存器用于主键 id 只有一个 long 类型字段的数据
 *
 * @param <CO> 缓存对象的类型
 * @author f
 */
public abstract class RedisHashBuffer<CO extends Cacheable> extends RedisBaseHashBuffer<CO> {

    /**
     * 从数据库中查不到的 co 对象的生成器，需要设置 id 和 热数据，防止 RedisBuffer 查询数据库
     */
    private final Function<List<Long>, Map<Long, CO>> deletedCOGenerator;

    private final RedisHashMapper<CO> mapper;

    private final RedisHashBufferKeyBuilder keyBuilder;

    public RedisHashBuffer(RedisTemplate<String, Object> redisTemplate,
                           RedisHashBufferKeyBuilder keyBuilder,
                           int cacheExpire,
                           Class<CO> coClazz,
                           RedisHashMapper<CO> mapper) {
        this(
                redisTemplate, keyBuilder,
                cacheExpire, coClazz,
                mapper, deletedCOIdList -> Map.of()
        );
    }

    public RedisHashBuffer(RedisTemplate<String, Object> redisTemplate,
                           RedisHashBufferKeyBuilder keyBuilder,
                           int cacheExpire,
                           Class<CO> coClazz,
                           RedisHashMapper<CO> mapper,
                           Function<List<Long>, Map<Long, CO>> deletedCOGenerator) {
        super(redisTemplate, cacheExpire, coClazz);

        this.mapper = mapper;
        this.keyBuilder = keyBuilder;
        this.deletedCOGenerator = deletedCOGenerator;
    }

    /**
     * 批量缓存数据
     *
     * @param coMap 待缓存的数据映射，key：缓存数据的 id，value：缓存的数据
     */
    public void cacheBatch(Map<Long, CO> coMap) {
        if (CollectionUtils.isEmpty(coMap)) {
            return;
        }

        super.executePipelined(operations -> coMap.forEach((id, co) -> {
            String cacheKey = keyBuilder.buildHashBufferKey(id);
            operations.opsForHash().putAll(cacheKey, BeanUtil.beanToMap(co));
            operations.expire(cacheKey, cacheExpire, TimeUnit.SECONDS);
        }));
    }

    /**
     * 获取缓存的数据
     *
     * @param id 待获取数据的缓存的 id
     * @return 缓存的数据
     */
    public CO get(long id) {
        return checkCOAndRepair(id,
                fromEntries(redisTemplate.opsForHash().entries(keyBuilder.buildHashBufferKey(id))));
    }

    /**
     * 获取缓存的部分数据
     *
     * @param id            待获取数据的缓存的 id
     * @param entryNameList 指定对象的部分字段的名称集合
     * @return 缓存的部分数据
     */
    protected CO getLess(long id, List<String> entryNameList) {
        return checkCOAndRepair(id, fromEntries(getEntries(
                keyBuilder.buildHashBufferKey(id),
                entryNameList, redisTemplate.opsForHash()
        )));
    }

    // 检查传入的 co 有没有 缺失热数据、缺失 的问题，如果有，则补充
    private CO checkCOAndRepair(long id, CO co) {
        try {
            // 如果缓存对象的 id 为 null，则这个缓存对象是缺失的
            if (co.id() == null) {
                return caching(List.of(id)).get(id);
            }

            // 如果第一个热数据字段的值为 null，则这个缓存对象是不完整的
            if (hotFieldGetters[0].invoke(co) == null) {
                replenish(List.of(co));
            }

            return co;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取缓存的数据
     *
     * @param idList 待获取数据的缓存的 id 集合
     * @return 缓存的数据映射，key：缓存数据的 id，value：缓存的数据
     */
    public Map<Long, CO> map(Collection<Long> idList) {
        List<CO> incompleteCOList = new ArrayList<>();
        List<Long> missingCOIdList = new ArrayList<>();

        List<CO> coList = new ArrayList<>();
        super.execute(operations -> idList.forEach(id -> checkCOAndRecord(
                fromEntries(operations.opsForHash().entries(keyBuilder.buildHashBufferKey(id))),
                incompleteCOList, missingCOIdList, coList, id
        )));

        return getLongCOMap(coList, missingCOIdList, incompleteCOList);
    }

    // 检查传入的 co 有没有 缺失热数据、缺失 的问题，如果有，则加入相应的集合中
    private void checkCOAndRecord(CO co, List<CO> incompleteCOList,
                                  List<Long> missingCOIdList, List<CO> coList, Long id) {
        try {
            if (co.id() == null) {
                // 如果缓存对象的 id 为 null，则这个缓存对象是缺失的
                missingCOIdList.add(id);
            } else if (hotFieldGetters[0].invoke(co) == null) {
                // 如果第一个热数据字段的值为 null，则这个缓存对象是不完整的
                incompleteCOList.add(co);
                coList.add(co);
            } else {
                coList.add(co);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取缓存的部分数据
     *
     * @param idCollection  待获取数据的缓存的 id 集合
     * @param entryNameList 指定对象的部分字段的名称集合
     * @return 缓存的部分数据映射，key：缓存数据的 id，value：缓存的部分数据
     */
    protected Map<Long, CO> mapLess(Collection<Long> idCollection, List<String> entryNameList) {
        List<CO> incompleteCOList = new ArrayList<>();
        List<Long> missingCOIdList = new ArrayList<>();

        List<CO> coList = new ArrayList<>();
        super.execute(operations -> idCollection.forEach(id -> checkCOAndRecord(
                fromEntries(getEntries(
                        keyBuilder.buildHashBufferKey(id), entryNameList, operations.opsForHash()
                )), incompleteCOList, missingCOIdList, coList, id)
        ));

        return getLongCOMap(coList, missingCOIdList, incompleteCOList);
    }

    // 将原本的 缓存 list 集合 转换成 缓存映射，key：缓存的 id，value：缓存对象，顺便补充缺少的(热)数据
    private Map<Long, CO> getLongCOMap(List<CO> coList, List<Long> missingCOIdList, List<CO> incompleteCOList) {
        Map<Long, CO> coMap = coList.stream().collect(Collectors.toMap(Cacheable::id, Function.identity()));

        // 补充缺少的数据
        if (!missingCOIdList.isEmpty()) {
            coMap.putAll(caching(missingCOIdList));
        }

        // 补充缺少的热数据
        incompleteCOList = incompleteCOList.stream().filter(co -> co.id() != null).toList();
        if (!incompleteCOList.isEmpty()) {
            replenish(incompleteCOList);
        }

        return coMap;
    }

    // 查询没有找到的 co 对象，并将其缓存起来
    private Map<Long, CO> caching(List<Long> missingCOIdList) {
        // 查询没有找到的 co 对象
        Map<Long, CO> missingCOMap = getCoList(missingCOIdList).stream().collect(Collectors.toMap(
                Cacheable::id,
                Function.identity()
        ));

        // 如果在数据库中找不到 co 对象，则说明 co 对象被删除，这时根据 子缓存器生成新对象的策略 生成新的对象，并暂时缓存起来
        List<Long> deletedCOIdList = missingCOIdList.stream()
                .filter(missingCOId -> !missingCOMap.containsKey(missingCOId)).toList();
        if (!deletedCOIdList.isEmpty()) {
            missingCOMap.putAll(deletedCOGenerator.apply(deletedCOIdList));
        }

        // 缓存没有找到的 co 对象
        cacheBatch(missingCOMap);
        return missingCOMap;
    }

    // 查询没有找到的 co 对象
    private List<CO> getCoList(List<Long> missingCOIdList) {
        List<CO> missingCOList = mapper.listData(missingCOIdList);

        // 从其它数据库中查询数据，如果数据为 null，则直接返回
        Map<Long, ?> extraInfoMap = mapExtraFromOtherDB(missingCOIdList);
        if (extraInfoMap == null) {
            return missingCOList;
        }

        // 将额外数据设置到每一个 co 对象中
        missingCOList.forEach(missingCO -> {
            Object extraInfo = extraInfoMap.get(missingCO.id());
            if (extraInfo != null) {
                setExtraInfo(missingCO, extraInfo);
            }
        });

        return missingCOList;
    }

    /**
     * 从其它数据库中获取指定缓存的剩余数据，留给需要查询其它数据库的子缓存器实现
     * <p>
     * 注：本方法对于完全依赖 mysql 的数据无效，仅针对将部分数据存在其它数据库的数据
     *
     * @param coIdList 指定缓存的 id 集合
     * @return 其它数据库中指定缓存的剩余数据，key：co 的 id，value：co 的剩余数据；如果无需获取，则返回 null
     */
    protected Map<Long, ?> mapExtraFromOtherDB(List<Long> coIdList) {
        return null;
    }

    /**
     * 将额外数据设置到 co 对象中，留给需要查询其它数据库的子缓存器实现
     * <p>
     * 注：本方法对于完全依赖 mysql 的数据无效，仅针对将部分数据存在其它数据库的数据
     *
     * @param co        指定的缓存
     * @param extraInfo 缓存的额外数据
     */
    protected void setExtraInfo(CO co, Object extraInfo) {
    }

    // 补充缓存对象缺少的热数据
    private void replenish(List<CO> incompleteCOList) {
        Map<Long, CO> incompleteCOMap = incompleteCOList.stream().collect(Collectors.toMap(
                Cacheable::id,
                Function.identity()
        ));
        // 补充热数据
        mapper.listHotData(incompleteCOMap.keySet()).forEach(hotData -> {
            try {
                CO incompleteCO = incompleteCOMap.get(hotData.id());
                for (int i = 0; i < hotFieldSetters.length; i++) {
                    Method hotFieldSetter = hotFieldSetters[i];
                    Method hotFieldGetter = hotFieldGetters[i];
                    hotFieldSetter.invoke(incompleteCO, hotFieldGetter.invoke(hotData));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        // 缓存整个缓存对象
        cacheBatch(incompleteCOMap);
    }

    /**
     * 清除缓存中的热数据
     *
     * @param id 待清除热数据的缓存的 id
     */
    public void cleanHot(long id) {
        redisTemplate.opsForHash().delete(keyBuilder.buildHashBufferKey(id), hotFieldsName);
    }

    /**
     * 清除缓存数据
     *
     * @param id 待清除的缓存的 id
     */
    public void clean(long id) {
        redisTemplate.delete(keyBuilder.buildHashBufferKey(id));
    }

    /**
     * 批量清除缓存数据
     *
     * @param idList 待清除的缓存的 id 集合
     */
    public void cleanBatch(List<Long> idList) {
        redisTemplate.delete(idList.stream().map(keyBuilder::buildHashBufferKey).toList());
    }
}
